/*-------------------------------------------------------------------------
 *
 * 文件处理工具例程。
 *
 * 各种用于处理文件的工具函数。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/common/file_utils.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#include "common/file_utils.h"
#ifdef FRONTEND
#include "common/logging.h"
#endif

#ifdef FRONTEND

/* 如果我们有 pg_flush_data 的实现，定义 PG_FLUSH_DATA_WORKS */
#if defined(HAVE_SYNC_FILE_RANGE)
#define PG_FLUSH_DATA_WORKS 1
#elif defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_DONTNEED)
#define PG_FLUSH_DATA_WORKS 1
#endif

/*
 * pg_xlog 在版本 10 中已重命名为 pg_wal。
 */
#define MINIMUM_VERSION_FOR_PG_WAL	100000

#ifdef PG_FLUSH_DATA_WORKS
static int	fc_pre_sync_fname(const char *fc_fname, bool fc_isdir);
#endif
static void fc_walkdir(const char *fc_path,
					int (*fc_action) (const char *fname, bool isdir),
					bool fc_process_symlinks);

/*
 * 对 PGDATA 及其所有内容递归地调用 fsync。
 *
 * 我们会在任何地方 fsync 普通文件和目录，但仅在 pg_wal（或 pg_xlog）及其下方的 pg_tblspc 中跟随符号链接。
 * 其他符号链接被假定指向我们不负责 fsync 的文件，并且可能没有写入的权限。
 *
 * serverVersion 指示要 fsync 的服务器的版本。
 */
void fsync_pgdata(const char *fc_pg_data,
			 int fc_serverVersion)
{
	bool		fc_xlog_is_symlink;
	char		fc_pg_wal[MAXPGPATH];
	char		fc_pg_tblspc[MAXPGPATH];

	/* 处理在 post-10 集群中将 pg_xlog 重命名为 pg_wal */
	snprintf(fc_pg_wal, MAXPGPATH, "%s/%s", fc_pg_data,
			 fc_serverVersion < MINIMUM_VERSION_FOR_PG_WAL ? "pg_xlog" : "pg_wal");
	snprintf(fc_pg_tblspc, MAXPGPATH, "%s/pg_tblspc", fc_pg_data);

	/*
	 * 如果 pg_wal 是一个符号链接，我们需要单独递归进入它，
	 * 因为下面的第一个 walkdir 会忽略它。
	 */
	fc_xlog_is_symlink = false;

	{
		struct stat fc_st;

		if (lstat(fc_pg_wal, &fc_st) < 0)
			pg_log_error("could not stat file \"%s\": %m", fc_pg_wal);
		else if (S_ISLNK(fc_st.st_mode))
			fc_xlog_is_symlink = true;
	}

	/*
	 * 如果可能，提示内核我们即将对数据目录及其内容进行 fsync。
	 */
#ifdef PG_FLUSH_DATA_WORKS
	fc_walkdir(fc_pg_data, fc_pre_sync_fname, false);
	if (fc_xlog_is_symlink)
		fc_walkdir(fc_pg_wal, fc_pre_sync_fname, false);
	fc_walkdir(fc_pg_tblspc, fc_pre_sync_fname, true);
#endif

	/*
	 * 现在我们以相同的顺序执行 fsync()。
	 *
	 * 主要调用会忽略符号链接，因此除了在 pg_wal 是符号链接时进行特别处理外，
	 * pg_tblspc 必须单独访问，process_symlinks = true。请注意，如果 pg_tblspc 中有任何普通目录，
	 * 它们将被 fsync 两次。这不是预期的情况，所以我们不担心优化它。
	 */
	fc_walkdir(fc_pg_data, fsync_fname, false);
	if (fc_xlog_is_symlink)
		fc_walkdir(fc_pg_wal, fsync_fname, false);
	fc_walkdir(fc_pg_tblspc, fsync_fname, true);
}

/*
 * 对给定目录及其所有内容递归地调用 fsync。
 *
 * 这是 walkdir() 上的一个便捷包装器。
 */
void fsync_dir_recurse(const char *fc_dir)
{
	/*
	 * 如果可能，提示内核我们即将对数据目录及其内容进行 fsync。
	 */
#ifdef PG_FLUSH_DATA_WORKS
	fc_walkdir(fc_dir, fc_pre_sync_fname, false);
#endif

	fc_walkdir(fc_dir, fsync_fname, false);
}

/*
 * walkdir: 递归地遍历目录，对每个普通文件和目录（包括命名目录本身）应用操作。
 *
 * 如果 process_symlinks 为 true，则该操作和递归也适用于指向给定目录中的符号链接的普通文件和目录；
 * 否则符号链接会被忽略。符号链接在子目录中始终被忽略，即我们故意不将 process_symlinks 标志传递给递归调用。
 *
 * 错误会被报告，但不被视为致命。
 *
 * 另请参见 fd.c 中的 walkdir，它是此逻辑的后端版本。
 */
static void fc_walkdir(const char *fc_path,
		int (*fc_action) (const char *fname, bool isdir),
		bool fc_process_symlinks)
{
	DIR		   *fc_dir;
	struct dirent *fc_de;

	fc_dir = opendir(fc_path);
	if (fc_dir == NULL)
	{
		pg_log_error("could not open directory \"%s\": %m", fc_path);
		return;
	}

	while (errno = 0, (fc_de = readdir(fc_dir)) != NULL)
	{
		char		fc_subpath[MAXPGPATH * 2];

		if (strcmp(fc_de->d_name, ".") == 0 ||
			strcmp(fc_de->d_name, "..") == 0)
			continue;

		snprintf(fc_subpath, sizeof(fc_subpath), "%s/%s", fc_path, fc_de->d_name);

		switch (get_dirent_type(fc_subpath, fc_de, fc_process_symlinks, PG_LOG_ERROR))
		{
			case PGFILETYPE_REG:
				(*fc_action) (fc_subpath, false);
				break;
			case PGFILETYPE_DIR:
				fc_walkdir(fc_subpath, fc_action, false);
				break;
			default:

				/*
				 * 错误已由 get_dirent_type() 直接报告，
				 * 任何剩余的符号链接和未知文件类型都会被忽略。
				 */
				break;
		}
	}

	if (errno)
		pg_log_error("could not read directory \"%s\": %m", fc_path);

	(void) closedir(fc_dir);

	/*
	 * 重要的是 fsync 目标目录本身，因为单个文件的 fsync 并不能保证文件的目录项被同步。
	 * 最近版本的 ext4 已经使窗口宽得多，但这在过去一直是 ext3 和其他文件系统的问题。
	 */
	(*fc_action) (fc_path, true);
}

/*
 * 提示操作系统准备对该文件进行 fsync()。
 *
 * 忽略打开不可读文件时的错误，并以非致命方式报告其他错误。
 */
#ifdef PG_FLUSH_DATA_WORKS

static int fc_pre_sync_fname(const char *fc_fname, bool fc_isdir)
{
	int			fc_fd;

	fc_fd = open(fc_fname, O_RDONLY | PG_BINARY, 0);

	if (fc_fd < 0)
	{
		if (errno == EACCES || (fc_isdir && errno == EISDIR))
			return 0;
		pg_log_error("could not open file \"%s\": %m", fc_fname);
		return -1;
	}

	/*
	 * 我们在后端执行 pg_flush_data() 会做的事情：优先使用 sync_file_range，
	 * 但回退到 posix_fadvise。我们忽略错误，因为这只是一个提示。
	 */
#if defined(HAVE_SYNC_FILE_RANGE)
	(void) sync_file_range(fc_fd, 0, 0, SYNC_FILE_RANGE_WRITE);
#elif defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_DONTNEED)
	(void) posix_fadvise(fc_fd, 0, 0, POSIX_FADV_DONTNEED);
#else
#error PG_FLUSH_DATA_WORKS should not have been defined
#endif

	(void) close(fc_fd);
	return 0;
}

#endif							/* PG_FLUSH_DATA_WORKS */

/*
 * fsync_fname -- 尝试对文件或目录进行 fsync
 *
 * 忽略尝试打开不可读文件的错误，或在不允许/不需要对目录进行 fsync 的系统上进行的错误。所有其他错误都是致命的。
 */
int fsync_fname(const char *fc_fname, bool fc_isdir)
{
	int			fc_fd;
	int			fc_flags;
	int			fc_returncode;

	/*
	 * 一些操作系统要求目录以只读方式打开，而另一些系统不允许我们对以只读方式打开的文件进行 fsync；因此我们这里需要考虑这两种情况。使用 O_RDWR 将导致我们无法对用户ID没有写权限的文件进行 fsync，但我们认为这没问题。
	 */
	fc_flags = PG_BINARY;
	if (!fc_isdir)
		fc_flags |= O_RDWR;
	else
		fc_flags |= O_RDONLY;

	/*
	 * 打开文件，静默忽略关于不可读文件（或不支持的操作，例如在 Windows 上打开目录）的错误，并记录其他错误。
	 */
	fc_fd = open(fc_fname, fc_flags, 0);
	if (fc_fd < 0)
	{
		if (errno == EACCES || (fc_isdir && errno == EISDIR))
			return 0;
		pg_log_error("could not open file \"%s\": %m", fc_fname);
		return -1;
	}

	fc_returncode = fsync(fc_fd);

	/*
	 * 一些操作系统根本不允许我们对目录进行 fsync，因此我们可以忽略这些错误。其他任何错误都需要报告。
	 */
	if (fc_returncode != 0 && !(fc_isdir && (errno == EBADF || errno == EINVAL)))
	{
		pg_log_error("could not fsync file \"%s\": %m", fc_fname);
		(void) close(fc_fd);
		exit(EXIT_FAILURE);
	}

	(void) close(fc_fd);
	return 0;
}

/*
 * fsync_parent_path -- 对文件或目录的父路径进行 fsync
 *
 * 这旨在确保在操作系统崩溃或电源故障时，文件操作在磁盘上是持久的。
 */
int fsync_parent_path(const char *fc_fname)
{
	char		fc_parentpath[MAXPGPATH];

	strlcpy(fc_parentpath, fc_fname, MAXPGPATH);
	get_parent_directory(fc_parentpath);

	/*
	 * get_parent_directory() 如果输入参数只是文件名，则返回空字符串（参见 path.c 中的注释），因此将其视为当前目录。
	 */
	if (strlen(fc_parentpath) == 0)
		strlcpy(fc_parentpath, ".", MAXPGPATH);

	if (fsync_fname(fc_parentpath, true) != 0)
		return -1;

	return 0;
}

/*
 * durable_rename -- rename(2) 的包装，发出为了持久性所需的 fsync
 *
 * rename 的包装，类似于后端版本。
 */
int durable_rename(const char *fc_oldfile, const char *fc_newfile)
{
	int			fc_fd;

	/*
	 * 首先对旧路径和目标路径（如果存在）进行 fsync，以确保它们在磁盘上是持久的。同步目标文件并不是绝对必要的，但这使得在崩溃时更容易推理；因为这时可以保证在崩溃后源文件或目标文件至少存在一个。
	 */
	if (fsync_fname(fc_oldfile, false) != 0)
		return -1;

	fc_fd = open(fc_newfile, PG_BINARY | O_RDWR, 0);
	if (fc_fd < 0)
	{
		if (errno != ENOENT)
		{
			pg_log_error("could not open file \"%s\": %m", fc_newfile);
			return -1;
		}
	}
	else
	{
		if (fsync(fc_fd) != 0)
		{
			pg_log_error("could not fsync file \"%s\": %m", fc_newfile);
			close(fc_fd);
			exit(EXIT_FAILURE);
		}
		close(fc_fd);
	}

	/* 该是做真正的事情的时候了... */
	if (rename(fc_oldfile, fc_newfile) != 0)
	{
		pg_log_error("could not rename file \"%s\" to \"%s\": %m",
					 fc_oldfile, fc_newfile);
		return -1;
	}

	/*
	 * 为了保证重命名文件是持久的，对其新名称的文件及其包含的目录进行 fsync。
	 */
	if (fsync_fname(fc_newfile, false) != 0)
		return -1;

	if (fsync_parent_path(fc_newfile) != 0)
		return -1;

	return 0;
}

#endif							/* FRONTEND */

/*
 * 返回目录项的类型。
 *
 * 在前端代码中，elevel 应该是来自 logging.h 的级别；在后端代码中，它应该是来自 elog.h 的级别。
 */
PGFileType
get_dirent_type(const char *fc_path,
				const struct dirent *fc_de,
				bool fc_look_through_symlinks,
				int fc_elevel)
{
	PGFileType	fc_result;

	/*
	 * 一些系统在 dirent 结构中直接告诉我们类型，但这是 BSD 和 Linux 的扩展，POSIX 并不要求。即使接口存在，某些情况下，类型也是未知的，这取决于文件系统。
	 */
#if defined(DT_REG) && defined(DT_DIR) && defined(DT_LNK)
	if (fc_de->d_type == DT_REG)
		fc_result = PGFILETYPE_REG;
	else if (fc_de->d_type == DT_DIR)
		fc_result = PGFILETYPE_DIR;
	else if (fc_de->d_type == DT_LNK && !fc_look_through_symlinks)
		fc_result = PGFILETYPE_LNK;
	else
		fc_result = PGFILETYPE_UNKNOWN;
#else
	fc_result = PGFILETYPE_UNKNOWN;
#endif

	if (fc_result == PGFILETYPE_UNKNOWN)
	{
		struct stat fc_fst;
		int			fc_sret;


		if (fc_look_through_symlinks)
			fc_sret = stat(fc_path, &fc_fst);
		else
			fc_sret = lstat(fc_path, &fc_fst);

		if (fc_sret < 0)
		{
			fc_result = PGFILETYPE_ERROR;
#ifdef FRONTEND
			pg_log_generic(fc_elevel, PG_LOG_PRIMARY, "could not stat file \"%s\": %m", fc_path);
#else
			ereport(fc_elevel,
					(errcode_for_file_access(),
					 errmsg("could not stat file \"%s\": %m", fc_path)));
#endif
		}
		else if (S_ISREG(fc_fst.st_mode))
			fc_result = PGFILETYPE_REG;
		else if (S_ISDIR(fc_fst.st_mode))
			fc_result = PGFILETYPE_DIR;
		else if (S_ISLNK(fc_fst.st_mode))
			fc_result = PGFILETYPE_LNK;
	}

	return fc_result;
}
